<template>
  <div
    ref="slider"
    :class="{
      'el-slider--vertical': vertical,
      'is-dragging': dragging
    }"
    class="el-slider"
    @click="onSliderClick"
  >
    <div :style="runwayStyle" class="el-slider__runway">
      <div :style="barStyle" class="el-slider__bar"></div>
      <div
        ref="button"
        :style="buttonWrapperStyle"
        class="el-slider__button-wrapper"
        @mousedown="onButtonDown"
        @touchstart="onButtonDown"
      >
        <div class="el-slider__button"></div>
      </div>
      <div v-if="showTooltip" :style="tooltipStyle" class="el-slider__tooltip">
        {{ currentValue }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'

const props = defineProps({
  modelValue: {
    type: Number,
    default: 0
  },
  min: {
    type: Number,
    default: 0
  },
  max: {
    type: Number,
    default: 100
  },
  step: {
    type: Number,
    default: 1
  },
  vertical: {
    type: Boolean,
    default: false
  },
  height: {
    type: String,
    default: '300px'
  },
  showTooltip: {
    type: Boolean,
    default: true
  },
  tooltipPosition: {
    type: String,
    default: 'top' // 'top' or 'bottom' for horizontal, 'left' or 'right' for vertical
  }
})

const emit = defineEmits(['update:modelValue', 'change', 'input'])

const slider = ref(null)
const button = ref(null)
const dragging = ref(false)
const startX = ref(0)
const startY = ref(0)
const startPosition = ref(0)
const currentValue = ref(props.modelValue)

const runwayStyle = computed(() => {
  return props.vertical ? { height: props.height } : {}
})

const barStyle = computed(() => {
  return props.vertical
    ? {
        height: `${((currentValue.value - props.min) / (props.max - props.min)) * 100}%`,
        bottom: '0'
      }
    : {
        width: `${((currentValue.value - props.min) / (props.max - props.min)) * 100}%`
      }
})

const buttonWrapperStyle = computed(() => {
  return props.vertical
    ? {
        bottom: `${((currentValue.value - props.min) / (props.max - props.min)) * 100}%`
      }
    : {
        left: `${((currentValue.value - props.min) / (props.max - props.min)) * 100}%`
      }
})

const tooltipStyle = computed(() => {
  if (props.vertical) {
    return props.tooltipPosition === 'left'
      ? { right: '32px', top: '50%', transform: 'translateY(-50%)' }
      : { left: '32px', top: '50%', transform: 'translateY(-50%)' }
  } else {
    return props.tooltipPosition === 'top'
      ? { bottom: '32px', left: '50%', transform: 'translateX(-50%)' }
      : { top: '32px', left: '50%', transform: 'translateX(-50%)' }
  }
})

const setPosition = (percent) => {
  const targetValue = props.min + percent * (props.max - props.min)
  const steppedValue = Math.round(targetValue / props.step) * props.step
  const clampedValue = Math.min(props.max, Math.max(props.min, steppedValue))

  if (currentValue.value !== clampedValue) {
    currentValue.value = clampedValue
    emit('update:modelValue', currentValue.value)
    emit('input', currentValue.value)
  }
}

const onSliderClick = (event) => {
  if (dragging.value) return
  if (!slider.value) return

  const sliderRect = slider.value.getBoundingClientRect()
  let percent

  if (props.vertical) {
    const clientY = event.type === 'touchstart' ? event.touches[0].clientY : event.clientY
    percent = (sliderRect.bottom - clientY) / sliderRect.height
  } else {
    const clientX = event.type === 'touchstart' ? event.touches[0].clientX : event.clientX
    percent = (clientX - sliderRect.left) / sliderRect.width
  }

  setPosition(percent)
  emit('change', currentValue.value)
}

const onButtonDown = (event) => {
  event.preventDefault()
  if (event.type === 'touchstart') {
    event.stopPropagation()
  }

  dragging.value = true
  button.value.querySelector('.el-slider__button').style.width = '16px'
  button.value.querySelector('.el-slider__button').style.height = '16px'
  button.value.style.top = '-5px'
  if (props.vertical) {
    startY.value = event.type === 'touchstart' ? event.touches[0].clientY : event.clientY
  } else {
    startX.value = event.type === 'touchstart' ? event.touches[0].clientX : event.clientX
  }
  startPosition.value = (currentValue.value - props.min) / (props.max - props.min)

  document.addEventListener('mousemove', onDragging)
  document.addEventListener('touchmove', onDragging)
  document.addEventListener('mouseup', onDragEnd)
  document.addEventListener('touchend', onDragEnd)
}

const onDragging = (event) => {
  if (!dragging.value) return
  if (!slider.value) return

  const sliderRect = slider.value.getBoundingClientRect()
  let percent

  if (props.vertical) {
    const clientY = event.type === 'mousemove' ? event.clientY : event.touches[0].clientY
    const deltaY = startY.value - clientY
    const totalY = sliderRect.height
    percent = startPosition.value + deltaY / totalY
  } else {
    const clientX = event.type === 'mousemove' ? event.clientX : event.touches[0].clientX
    const deltaX = clientX - startX.value
    const totalX = sliderRect.width
    percent = startPosition.value + deltaX / totalX
  }

  setPosition(percent)
}

const onDragEnd = () => {
  if (!dragging.value) return

  dragging.value = false
  document.removeEventListener('mousemove', onDragging)
  document.removeEventListener('touchmove', onDragging)
  document.removeEventListener('mouseup', onDragEnd)
  document.removeEventListener('touchend', onDragEnd)

  emit('change', currentValue.value)
}

watch(
  () => props.modelValue,
  (val) => {
    if (!dragging.value) {
      currentValue.value = val
    }
  }
)

onMounted(() => {
  currentValue.value = props.modelValue
})

onBeforeUnmount(() => {
  document.removeEventListener('mousemove', onDragging)
  document.removeEventListener('touchmove', onDragging)
  document.removeEventListener('mouseup', onDragEnd)
  document.removeEventListener('touchend', onDragEnd)
})
</script>

<style scoped>
.el-slider {
  width: 100%;
  height: auto;
  position: relative;
  //margin: 10px 0;
}

.el-slider__runway {
  width: 100%;
  height: 6px;
  background-color: #e4e7ed;
  border-radius: 3px;
  position: relative;
  cursor: pointer;
}

.el-slider__runway:hover .el-slider__button {
  width: 16px;
  height: 16px;
}

.el-slider__runway:hover .el-slider__button-wrapper {
  top: -5px;
}

.el-slider--vertical .el-slider__runway {
  width: 6px;
  height: 100%;
  margin: 0 auto;
}

.el-slider__bar {
  transition: all 0.1s;
  height: 6px;
  background-color: #409eff;
  border-radius: 3px;
  position: absolute;
  top: 0;
  left: 0;
}

.el-slider--vertical .el-slider__bar {
  width: 6px;
  bottom: 0;
  top: auto;
}

.el-slider__button-wrapper {
  transition: all 0.1s;
  position: absolute;
  top: -2px;
  transform: translateX(-50%);
  background-color: transparent;
  z-index: 1;
  cursor: grab;
}

.el-slider__button-wrapper:hover .el-slider__button {
  width: 16px;
  height: 16px;
}

.el-slider__button-wrapper:hover {
  top: -5px;
}

.el-slider--vertical .el-slider__button-wrapper {
  top: auto;
  left: -15px;
  transform: translateY(50%);
}

.el-slider__button {
  width: 10px;
  height: 10px;
  border: 2px solid #409eff;
  background-color: #fff;
  border-radius: 50%;
  transition: 0.2s;
  user-select: none;
}

.el-slider.is-dragging .el-slider__button {
  transform: scale(1.2);
  cursor: grabbing;
}

.el-slider__tooltip {
  position: absolute;
  background-color: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
  white-space: nowrap;
  z-index: 2;
}

.el-slider__tooltip::after {
  content: '';
  position: absolute;
  width: 0;
  height: 0;
  border: 5px solid transparent;
}

.el-slider:not(.el-slider--vertical) .el-slider__tooltip[style*='top']::after {
  top: 100%;
  left: 50%;
  margin-left: -5px;
  border-top-color: rgba(0, 0, 0, 0.7);
}

.el-slider:not(.el-slider--vertical) .el-slider__tooltip[style*='bottom']::after {
  bottom: 100%;
  left: 50%;
  margin-left: -5px;
  border-bottom-color: rgba(0, 0, 0, 0.7);
}

.el-slider--vertical .el-slider__tooltip[style*='left']::after {
  left: 100%;
  top: 50%;
  margin-top: -5px;
  border-left-color: rgba(0, 0, 0, 0.7);
}

.el-slider--vertical .el-slider__tooltip[style*='right']::after {
  right: 100%;
  top: 50%;
  margin-top: -5px;
  border-right-color: rgba(0, 0, 0, 0.7);
}
</style>
